/*
 * 
 */
package test.smiddus.nl;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.provider.Settings;
import java.text.DecimalFormat;
import java.util.Calendar;

import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.TextView;
import android.widget.Toast;

// TODO: Auto-generated Javadoc
/**
 * The Class Sensors.
 */
public class Rowmode extends Activity implements SensorEventListener{
	
	private static final int SCREEN_TIMEOUT_DELAY = 3000000;
	private static final int INTERPOL_FREQ = 20;
	private static final int SAMPLE_FREQ = 10; //Hz
	private static final int NR_SAMPLES = 256;//should be a power of 2 for efficient FFT. Should be >= 16, otherwise calculation of weightedIndx fails with IndexOutOfBounds error .
	private static final int PADDING = 3;
	//Derived constants
	private static final int SAMPLE_FACTOR = INTERPOL_FREQ/SAMPLE_FREQ;
	private static final int INTERPOL_ARRAYSIZE = (int)(((double)NR_SAMPLES/SAMPLE_FREQ)*INTERPOL_FREQ);
	private static final int INTERPOL_STEP = 1000/INTERPOL_FREQ;
	private static final int NYQUIST_FREQ = SAMPLE_FREQ/2;
	private static final int BLOCKSIZE = INTERPOL_ARRAYSIZE/4; //new samples before doing another FFT. Also must be a power of 2
	
	//double[] Gauss = {0, 0, 1,0 , 0};//noblur
	//double[] Gauss = {0, 0.1065, 0.7866,0.1065, 0};//Sigma=0.5
	double[] Gauss = {0.0545, 0.2442, 0.4026, 0.2442, 0.0545};//Sigma=1
	//double[] Gauss = {0.0702, 0.1311, 0.1907, 0.2161, 0.1907, 0.1311, 0.0702};//Sigma=2
	
	private View mView;
	Window mWindow = null;
	
	private int n, nu;
	int defTimeOut = 0;
	long startTime;
	int previousTime = 0;
	int timeWindow;
	
	float[] interpolatedReadings = new float[INTERPOL_ARRAYSIZE + (2*PADDING)];//padded with 3 zeros on both sides for convolution
	float[] sampledReadings = new float[NR_SAMPLES];
	private boolean initialArray = true;
	private boolean showReadings = false;
	float previousReading = 0;
	int currentSamplepoint = INTERPOL_STEP;
	int indx = PADDING+1; 
	int startIndx = 0 + PADDING;
	int stopIndx = interpolatedReadings.length - 1;
	
	boolean mExternalStorageAvailable = false;
	boolean mExternalStorageWriteable = false;	
	boolean writerExists = false;
	//debug flags
	boolean writeLog = true; 
	boolean setBrightness = true;	
	boolean setTimeout = true;
	float oldBrightness;

	String state = Environment.getExternalStorageState();
	TextView accel_x, accel_y, accel_z;	
	SensorManager senmg;
	Sensor accelerometer;
	FileWriter writer_std;
	FileWriter writer_intp;
	FileWriter writer_samp;
	FileWriter writer_tempo;
	DecimalFormat round = new DecimalFormat("0.000000"); 
	WindowManager.LayoutParams lp;
	
	/* (non-Javadoc)
	 * @see android.app.Activity#onCreate(android.os.Bundle)
	 */
	@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.rowmode);
        mView = findViewById(R.id.RowmodeMain);
        mView.setVisibility(View.INVISIBLE);
        Toast.makeText( this, "To view sensor readings choose 'Show readings' from the options menu.", Toast.LENGTH_LONG ).show();
        Toast.makeText( this, "Warning: keeping the readings visible all the time will reduce batterylife significantly.", Toast.LENGTH_LONG ).show();
        //Make activity screen as dark as possible (override brightness settings of device)
        //TODO remove if
        if (setBrightness){
	        mWindow = getWindow();
	    	lp = mWindow.getAttributes();
	    	oldBrightness = lp.screenBrightness;
	    	lp.screenBrightness = (float) 0.01;
	    	mWindow.setAttributes(lp);
	    	Log.d("brightness","brightness set to 0.01");     	
        }
        
        /*Temporary override device settings for screen timeout. Set to 3000000 msec
         *This workaround is needed since the accelerometer sensors stop working when the display is
         *shut off on some popular devices (notably HTC devices).
         */
        //TODO make this device dependent
        //TODO remove if
        if (setTimeout){
        	defTimeOut = Settings.System.getInt(getContentResolver(), 
        			Settings.System.SCREEN_OFF_TIMEOUT, SCREEN_TIMEOUT_DELAY);
        	Settings.System.putInt(getContentResolver(), 
        			Settings.System.SCREEN_OFF_TIMEOUT, SCREEN_TIMEOUT_DELAY);
        	Log.d("screen_timeout","screen-off timeout set to 3000000 msec");
        }
	    
        //check whether external storage is available
        if (Environment.MEDIA_MOUNTED.equals(state)) {
        	mExternalStorageAvailable = mExternalStorageWriteable = true;
    	} else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
    	    // We can only read the media
    	    mExternalStorageAvailable = true;
    	    mExternalStorageWriteable = false;
    	} else {
    	    // Something else is wrong. It may be one of many other states, but all we need
    	    //  to know is we can neither read nor write
    	    mExternalStorageAvailable = mExternalStorageWriteable = false;
    	}
        	
		 //Acquire SensorManager
        senmg = (SensorManager) getSystemService(SENSOR_SERVICE); 
        accelerometer = senmg.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        //accel_x = (TextView) findViewById(R.id.accelerometer_x);
        //accel_y = (TextView) findViewById(R.id.accelerometer_y);
        //accel_z = (TextView) findViewById(R.id.accelerometer_z);
        
        startTime = System.currentTimeMillis();
        //Initialize padding elements with zeros
        for (int i = 0; i <= PADDING; i++){
        	interpolatedReadings[i] = 0;
        }
        for (int i = interpolatedReadings.length - PADDING; i < interpolatedReadings.length; i++){
        	interpolatedReadings[i] = 0;
        }
        initLogFiles(); 
	}
	
	public void onAccuracyChanged(Sensor sensor, int accuracy) {
		// TODO Auto-generated method stub
	}
	
	public void onSensorChanged(SensorEvent event) {
		int sensor_type = event.sensor.getType();
		synchronized (this) {
			if (sensor_type == Sensor.TYPE_ACCELEROMETER) {
				//accel_x.setText(round.format(event.values[0]) + " m/s^2");
				//accel_y.setText(round.format(event.values[1]) + " m/s^2");
				//accel_z.setText(round.format(event.values[2]) + " m/s^2");
				timeWindow = (int) (System.currentTimeMillis() - startTime);
								
				while (timeWindow >= currentSamplepoint && indx < stopIndx){
					//linear interpolation
					interpolatedReadings[indx] = (float) (previousReading + 
					(((currentSamplepoint-previousTime)/(double)(timeWindow-previousTime))* 
							(event.values[1]- previousReading)));
					
					if (writerExists){
						try{
							writer_intp.append(System.currentTimeMillis()+", ");
					        writer_intp.append(currentSamplepoint+", ");
					        writer_intp.append(round.format(interpolatedReadings[indx]));
					        writer_intp.append('\n');
					        writer_intp.flush();
					    }
				        catch(IOException e)
			         	{
			         	     e.printStackTrace();
			         	} 
					}	
					indx += 1;
					currentSamplepoint += INTERPOL_STEP;	
					
				}
				previousTime = timeWindow;
				previousReading = event.values[1];
				
				//interpolated array is filled
				if (indx == stopIndx){
					subsample();
					for (int i = 0; i < sampledReadings.length; i++){
						Log.i("sample", i+": "+ sampledReadings[i]);
					}
					//TODO check for faster Java FFT implementations or use JNI with KissFFT
					float[] transformedReadings = fftMag(sampledReadings);
					for (int i = 0; i < transformedReadings.length; i++){
						Log.i("freqPos", i + ": " + transformedReadings[i]);
					}
					//Check for maximum value within the expected frequency range [15,40] strokes/min
					int leftIndx = (int) Math.floor(0.25*(NR_SAMPLES/SAMPLE_FREQ));
					if (leftIndx < 2){leftIndx = 2;}
					int rightIndx = (int) Math.ceil(0.666666667*(NR_SAMPLES/SAMPLE_FREQ));
					Log.d("indices", "left: "+ leftIndx +" right: " + rightIndx);
					float max = 0;
					int maxIndx = 2;
					
					for (int i = leftIndx; i <= rightIndx; i++){ 
						if (transformedReadings[i] > max){
							maxIndx = i;
							max = transformedReadings[i];
						}
					}
					Log.d("tempo", "max index: " + maxIndx); 
				
					//Weight freq of max bin with magnitude of surrounding bins		
					float totalWeight = transformedReadings[maxIndx-2]+ 
						transformedReadings[maxIndx-1] + transformedReadings[maxIndx]+
						transformedReadings[maxIndx+1] + transformedReadings[maxIndx+2];
					Log.d("totWeight", "tot weight: "+ totalWeight);					
					float weightedIndx =  (maxIndx - 2) * (transformedReadings[maxIndx-2]/totalWeight) + 
						(maxIndx - 1) * (transformedReadings[maxIndx-1]/totalWeight) + 
						maxIndx * (transformedReadings[maxIndx]/totalWeight) + 
						(maxIndx + 1) * (transformedReadings[maxIndx+1]/totalWeight) + 
						(maxIndx + 2) * (transformedReadings[maxIndx+2]/totalWeight);
				
					Log.d("tempo", "weightedVal: "+ weightedIndx);
					
					float tempo = (weightedIndx*60)/(NR_SAMPLES/SAMPLE_FREQ);
					Log.d("tempo", "ESTIMATED TEMPO: "+ tempo);
					if (writerExists){
						try{
							writer_tempo.append(System.currentTimeMillis()+", ");
					        writer_tempo.append(currentSamplepoint+", ");
					        writer_tempo.append(round.format(tempo));
					        writer_tempo.append('\n');
					        writer_tempo.flush();
					    }
				        catch(IOException e)
			         	{
			         	     e.printStackTrace();
			         	} 
					}	
					
					//set new start and stop indices
					if (initialArray){
						stopIndx = startIndx + BLOCKSIZE + PADDING;
						initialArray = false;
					}
					//Circular array
					else{
						startIndx = ((stopIndx - (2* PADDING)) % INTERPOL_ARRAYSIZE) + PADDING;
						stopIndx = startIndx + BLOCKSIZE + PADDING;
					}
					indx = startIndx + PADDING;
						
					if ( startIndx == PADDING){
						//copy last elements of array to begin of array
						for (int i = 0;i < indx; i++){
							interpolatedReadings[i] = interpolatedReadings[INTERPOL_ARRAYSIZE - 1 + i];
						}
					}				
					Log.d("new indices", "startIndx: " + startIndx + " stopIndx: " + stopIndx);
				}
				
				//Write readings to log file
				if (writerExists){
					try{
						writer_std.append(System.currentTimeMillis()+"");
				        writer_std.append(',');
				        writer_std.append(timeWindow+"");
				        writer_std.append(',');
				        writer_std.append(round.format(event.values[0]));
				        writer_std.append(',');
				        writer_std.append(round.format(event.values[1]));
				        writer_std.append(',');
				        writer_std.append(round.format(event.values[2]));
				 
				        writer_std.append('\n');
				        writer_std.flush();
				    }
			        catch(IOException e)
		         	{
		         	     e.printStackTrace();
		         	} 
				}	
			}
	    }
	}
	 
	public void subsample(){
		//Circular array
		int j = (startIndx - PADDING) / SAMPLE_FACTOR;
		//blur only at the sample points 
		for (int i = startIndx; i < (stopIndx - PADDING); i += SAMPLE_FACTOR){
			sampledReadings[j % NR_SAMPLES] =  (float) (Gauss[0]* interpolatedReadings[i-2]+ 
					Gauss[1]* interpolatedReadings[i-1]+ Gauss[2]* interpolatedReadings[i]+ 
					Gauss[3]* interpolatedReadings[i+1]+ Gauss[4]* interpolatedReadings[i+2]);
			if (writerExists){
				try{
					writer_samp.append(System.currentTimeMillis()+", ");
			        writer_samp.append(currentSamplepoint+", ");
			        writer_samp.append(sampledReadings[j % NR_SAMPLES]+"");
			        writer_samp.append('\n');
			        writer_samp.flush();
			    }
		        catch(IOException e)
	         	{
	         	     e.printStackTrace();
	         	} 
			}	
			j++;
		}	
		Log.d("subsample","subsampling complete");
		
	}
	
	@Override
	    public boolean onCreateOptionsMenu(Menu menu) {
	        MenuInflater inflater = getMenuInflater();
//	        inflater.inflate(R.menu.sensor_delay_menu, menu);
	        inflater.inflate(R.menu.rowmode_menu, menu);
	        return true;
	    }
		    
    	/* (non-Javadoc)
    	 * @see android.app.Activity#onOptionsItemSelected(android.view.MenuItem)
    	 */
    	@Override
	    public boolean onOptionsItemSelected(MenuItem item) {
	        // Handle item selection
	        switch (item.getItemId()) {
	        case R.id.show_readings:
	        	if (showReadings){        	        		
	        		mView.setVisibility(View.INVISIBLE);   
	        		lp.screenBrightness = (float) 0.01;
	        		mWindow.setAttributes(lp);
	        		showReadings = false;
	        	}
	        	else{
	        		mView.setVisibility(View.VISIBLE);        		
	        		lp.screenBrightness = oldBrightness;
	    	    	mWindow.setAttributes(lp);
	    	    	showReadings = true; 
	        	}      	
	        case R.id.sensor_delay_fast:
	        	if (item.isChecked()) item.setChecked(false);
		        else item.setChecked(true);
	        	senmg.unregisterListener(this);
	        	senmg.registerListener(this, 
		                  accelerometer, 
		                  SensorManager.SENSOR_DELAY_FASTEST);
		        return true;
		    
	        case R.id.sensor_delay_game:
	        	if (item.isChecked()) item.setChecked(false);
		        else item.setChecked(true);
	        	senmg.unregisterListener(this);
	        	senmg.registerListener(this, 
		                  accelerometer, 
		                  SensorManager.SENSOR_DELAY_GAME);
		        return true;
		    
	        case R.id.sensor_delay_normal:
	        	if (item.isChecked()) item.setChecked(false);
		        else item.setChecked(true);
	        	senmg.unregisterListener(this);
	        	senmg.registerListener(this, 
		                  accelerometer, 
		                  SensorManager.SENSOR_DELAY_NORMAL);
		        return true;
	        case R.id.sensor_delay_ui:
	        	if (item.isChecked()) item.setChecked(false);
		        else item.setChecked(true);
	        	senmg.unregisterListener(this);
	        	senmg.registerListener(this, 
		                  accelerometer, 
		                  SensorManager.SENSOR_DELAY_UI);
		        return true;
		        
//	        case R.id.quit:
//	            this.finish();
	        default:
	            return super.onOptionsItemSelected(item);
	        }
	    }
 	
  	  private int bitrev(int j) {

  	    int j2;
  	    int j1 = j;
  	    int k = 0;
  	    for (int i = 1; i <= nu; i++) {
  	      j2 = j1/2;
  	      k  = 2*k + j1 - 2*j2;
  	      j1 = j2;
  	    }
  	    return k;
  	  }
  	  
  	  public final float[] fftMag(float[] x) {
  	    // assume n is a power of 2
  	    n = x.length;
  	    nu = (int)(Math.log(n)/Math.log(2));
  	    int n2 = n/2;
  	    int nu1 = nu - 1;
  	    float[] xre = new float[n];
  	    float[] xim = new float[n];
  	    float[] mag = new float[n2];
  	    float tr, ti, p, arg, c, s;
  	    int offset = (startIndx-PADDING)/SAMPLE_FACTOR; //ADDED 
  	    
  	    for (int i = 0; i < n; i++) {
  	      //xre[i] = x[i];
  	      //CHANGED adapted for circular array x:
  	    	xre[i] = x[(i + offset)% n];
  	    	xim[i] = 0.0f;
  	    }
  	    int k = 0;

  	    for (int l = 1; l <= nu; l++) {
  	      while (k < n) {
  	        for (int i = 1; i <= n2; i++) {
  	          p = bitrev (k >> nu1);
  	          arg = 2 * (float) Math.PI * p / n;
  	          c = (float) Math.cos (arg);
  	          s = (float) Math.sin (arg);
  	          tr = xre[k+n2]*c + xim[k+n2]*s;
  	          ti = xim[k+n2]*c - xre[k+n2]*s;
  	          xre[k+n2] = xre[k] - tr;
  	          xim[k+n2] = xim[k] - ti;
  	          xre[k] += tr;
  	          xim[k] += ti;
  	          k++;
  	        }
  	        k += n2;
  	      }
  	      k = 0;
  	      nu1--;
  	      n2 = n2/2;
  	    }
  	    k = 0;
  	    int r;
  	    while (k < n) {
  	      r = bitrev (k);
  	      if (r > k) {
  	        tr = xre[k];
  	        ti = xim[k];
  	        xre[k] = xre[r];
  	        xim[k] = xim[r];
  	        xre[r] = tr;
  	        xim[r] = ti;
  	      }
  	      k++;
  	    }

  	    mag[0] = (float) (Math.sqrt(xre[0]*xre[0] + xim[0]*xim[0]))/n;
  	    for (int i = 1; i < n/2; i++)
  	      mag[i]= 2 * (float) (Math.sqrt(xre[i]*xre[i] + xim[i]*xim[i]))/n;
  	    return mag;
  	 }
  	  
  	public void initLogFiles(){//Initialize log file
  		Calendar cal = Calendar.getInstance();
    	if (mExternalStorageWriteable && writeLog){
			Log.d("extStor", "External storage ok");
			File root = Environment.getExternalStorageDirectory();
			int hour = cal.get(Calendar.HOUR_OF_DAY);
			int minute = cal.get(Calendar.MINUTE);
			int day = cal.get(Calendar.DATE);
			int month = cal.get(Calendar.MONTH) + 1;
			int year = cal.get(Calendar.YEAR);
			File logfile_std = new File(root, "logfile_std_" + year + "-" + month + "-" + day + "-" + hour +  "h" + minute + ".csv");
			File logfile_intp = new File(root, "logfile_intp_" + year + "-" + month + "-" + day + "-" + hour +  "h" + minute + ".csv");
			File logfile_samp = new File(root, "logfile_samp_" + year + "-" + month + "-" + day + "-" + hour +  "h" + minute + ".csv");
			File logfile_tempo = new File(root, "logfile_tempo_" + year + "-" + month + "-" + day + "-" + hour +  "h" + minute + ".csv");
	        try{
	        	logfile_std.createNewFile();
	        }catch(IOException e){
	        	Log.e("IOException", "exception in createNewFile() method");
	        }
	        try{
	        	writerExists = true;
	        	writer_std = new FileWriter(logfile_std);      	
	        	writer_std.append("system time");
		        writer_std.append(',');
		        writer_std.append("msec passed");
		        writer_std.append(',');
		        writer_std.append("accel_x");
		        writer_std.append(',');
		        writer_std.append("accel_y");
		        writer_std.append(',');
		        writer_std.append("accel_z");
		        writer_std.append('\n');
		        
		        writer_intp = new FileWriter(logfile_intp);
	        	writer_intp.append("system time, ");
		        writer_intp.append("samplepoint, ");
		        writer_intp.append("accel_y");
		        writer_intp.append('\n');
		        
		        writer_samp = new FileWriter(logfile_samp);
	        	writer_samp.append("system time, ");
		        writer_samp.append("intp_samplepoint, ");
		        writer_samp.append("accel_y");
		        writer_samp.append('\n');
		        
		        writer_tempo = new FileWriter(logfile_tempo);
	        	writer_tempo.append("system time, ");
		        writer_tempo.append("msec passed, ");
		        writer_tempo.append("tempo");
		        writer_tempo.append('\n');
	        }
	        catch(IOException e)
         	{
         	     e.printStackTrace();
         	} 
       }
    }
    	@Override
		protected void onResume() {
		    super.onResume();
		    senmg.registerListener(this, 
		    		                  accelerometer, 
		    		                  SensorManager.SENSOR_DELAY_GAME);
		}
		
		/* (non-Javadoc)
		 * @see android.app.Activity#onDestroy()
		 */
		protected void onPause() {
			super.onPause();
			if (setTimeout){
				Settings.System.putInt(getContentResolver(), 
						Settings.System.SCREEN_OFF_TIMEOUT, defTimeOut);
			}
		    senmg.unregisterListener(this);
		    try {
		    	if (writerExists){
				writer_std.close();
				writer_intp.close();
				writer_samp.close();
				writer_tempo.close();
				}
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}    
		
}
